﻿using System;
using System.Windows;
using System.ComponentModel;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Windows.Threading;

using Microsoft.Win32;

namespace GrapeCity.Win32
{
    public class FolderBrowserDialog : CommonDialog
    {
        public FolderBrowserDialog()
        {
            this.Reset();
        }

        public string Description
        {
            get { return this._descriptionText; }
            set { this._descriptionText = (value == null) ? string.Empty : value; }
        }

        [DefaultValue(typeof(Environment.SpecialFolder), "Desktop")]
        public Environment.SpecialFolder RootFolder
        {
            get { return this._rootFolder; }
            set
            {
                if (!Enum.IsDefined(typeof(Environment.SpecialFolder), value))
                    throw new InvalidEnumArgumentException("value", (int)value, typeof(Environment.SpecialFolder));
                this._rootFolder = value;
            }
        }

        [DefaultValue("")]
        public string SelectedPath
        {
            get
            {
                if (this._selectedPath != null && this._selectedPath.Length != 0 && this._selectedPathNeedsCheck)
                    new FileIOPermission(FileIOPermissionAccess.PathDiscovery, this._selectedPath).Demand();
                return this._selectedPath;
            }
            set
            {
                this._selectedPath = (value == null) ? string.Empty : value;
                this._selectedPathNeedsCheck = false;
            }
        }

        [DefaultValue(true)]
        public bool ShowNewFolderButton
        {
            get { return this._showNewFolderButton; }
            set { this._showNewFolderButton = value; }
        }

        public override void Reset()
        {
            this._rootFolder = Environment.SpecialFolder.Desktop;
            this._descriptionText = string.Empty;
            this._selectedPath = string.Empty;
            this._selectedPathNeedsCheck = false;
            this._showNewFolderButton = true;
        }

        protected override bool RunDialog(IntPtr hwndOwner)
        {
            IntPtr ppfileLoc = IntPtr.Zero;
            bool pathSelected = false;
            UnsafeNativeMethods.Shell32.SHGetSpecialFolderLocation(hwndOwner, (int)this._rootFolder, ref ppfileLoc);
            if (ppfileLoc == IntPtr.Zero)
            {
                UnsafeNativeMethods.Shell32.SHGetSpecialFolderLocation(hwndOwner, 0, ref ppfileLoc);
                if (ppfileLoc == IntPtr.Zero)
                    throw new InvalidOperationException("FolderBrowserDialog has no root folder.");
            }
            int browseFlag = 0x40;
            if (!this._showNewFolderButton) 
                browseFlag += 0x200;
            if (System.Threading.Thread.CurrentThread.GetApartmentState() != System.Threading.ApartmentState.STA
                || Application.Current.Dispatcher.Thread != Dispatcher.CurrentDispatcher.Thread)
            {
                throw new InvalidOperationException("Cross thread invoking.");
            }
            IntPtr pidl = IntPtr.Zero;
            IntPtr hglobal = IntPtr.Zero;
            IntPtr pszPath = IntPtr.Zero;
            try
            {
                UnsafeNativeMethods.BROWSEINFO lpbi = new UnsafeNativeMethods.BROWSEINFO();
                hglobal = Marshal.AllocHGlobal((int)(260 * Marshal.SystemDefaultCharSize));
                pszPath = Marshal.AllocHGlobal((int)(260 * Marshal.SystemDefaultCharSize));
                this._callback = new UnsafeNativeMethods.BrowseCallbackProc(this.FolderBrowserDialog_BrowseCallbackProc);
                lpbi.pidlRoot = ppfileLoc;
                lpbi.hwndOwner = hwndOwner;
                lpbi.pszDisplayName = hglobal;
                lpbi.lpszTitle = this._descriptionText;
                lpbi.ulFlags = browseFlag;
                lpbi.lpfn = this._callback;
                lpbi.lParam = IntPtr.Zero;
                lpbi.iImage = 0;
                pidl = UnsafeNativeMethods.Shell32.SHBrowseForFolder(lpbi);
                if (pidl != IntPtr.Zero)
                {
                    UnsafeNativeMethods.Shell32.SHGetPathFromIDList(pidl, pszPath);
                    this._selectedPathNeedsCheck = true;
                    this._selectedPath = Marshal.PtrToStringAuto(pszPath);
                    pathSelected = true;
                }
            }
            finally
            {
                UnsafeNativeMethods.CoTaskMemFree(ppfileLoc);
                if (pidl != IntPtr.Zero) UnsafeNativeMethods.CoTaskMemFree(pidl);
                if (pszPath != IntPtr.Zero) Marshal.FreeHGlobal(pszPath);
                if (hglobal != IntPtr.Zero) Marshal.FreeHGlobal(hglobal);
                this._callback = null;
            }
            return pathSelected;
        }

        private static UnsafeNativeMethods.IMalloc GetSHMalloc()
        {
            UnsafeNativeMethods.IMalloc[] ppMalloc = new UnsafeNativeMethods.IMalloc[1];
            UnsafeNativeMethods.Shell32.SHGetMalloc(ppMalloc);
            return ppMalloc[0];
        }

        private int FolderBrowserDialog_BrowseCallbackProc(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData)
        {
            switch (msg)
            {
                case 1:
                    if (this._selectedPath.Length != 0)
                        UnsafeNativeMethods.SendMessage(new HandleRef(null, hwnd),
                            NativeMethods.BFFM_SETSELECTION,
                            1,
                            this._selectedPath);
                    break;
                case 2:
                    {
                        IntPtr pidl = lParam;
                        if (pidl != IntPtr.Zero)
                        {
                            IntPtr pszPath = Marshal.AllocHGlobal((int)(260 * Marshal.SystemDefaultCharSize));
                            bool flag = UnsafeNativeMethods.Shell32.SHGetPathFromIDList(pidl, pszPath);
                            Marshal.FreeHGlobal(pszPath);
                            UnsafeNativeMethods.SendMessage(new HandleRef(null, hwnd), 0x465, 0, flag ? 1 : 0);
                        }
                        break;
                    }
            }
            return 0;
        }

        private UnsafeNativeMethods.BrowseCallbackProc _callback;
        private string _descriptionText;
        private Environment.SpecialFolder _rootFolder;
        private string _selectedPath;
        private bool _selectedPathNeedsCheck;
        private bool _showNewFolderButton;
    }
}
